#include <stdlib.h>
#include <string.h>
#include <io.h>

#include <windows.h>

#include "lib.h"
#include "dir.h"
#include "opts.h"
#include "log.h"

#include "pkglist.h"
#include "pkgdesc.h"
#include "pathname.h"

PackageList::~PackageList()
{
	while (head)
	{
		tail = head;
		head = head->getNext();
		delete tail;
	}
	tail = (PackageDescription*)0;

	count = 0;
	changed = false;
}

bool8
PackageList::load()
{
	TCHAR inifile[MAX_PATH + 1];
	TCHAR zipfile[MAX_PATH + 1];
	TCHAR buf[256], which[16], *probe;
	int32 len, num, i;
	TCHAR *pkgname, *zipname;
	uint32 pkgver;

	//
	// todo: make this into a binary file...tampering with the file while
	//		 the patch server is running will cause problems on both the
	//		 client and the server.
	//		 Get rid of strip() also (this is the only func that calls it).
	//

	Log::msgInformation("Loading package list");
	lstrcpy(inifile, ServerOpts::patchPackagesList);

	len = GetPrivateProfileString(L"Packages", L"num", L"0", buf, 255, inifile);
	len = strip(buf, len);
	buf[len] = '\0';
	num = _wtol(buf);

	changed = false;

	for (i = 1; i <= num; i++)
	{
		_ltow(i, which, 10);
		
		len = GetPrivateProfileString(L"Packages", which, L"missing",
			buf, 255, inifile);
		len = strip(buf, len);
		buf[len] = '\0';

		if (0 == wcscmp(buf, L"missing"))
			continue;

		pkgname = buf;
		
		probe = wcschr(buf, L',');
		if (!probe)
			continue;
		*probe++ = '\0';

		pkgver = wcstoul(probe, &probe, 16);
		if (!probe || *probe != ',')
			continue;

		zipname = ++probe;

		String zipPath = ServerOpts::patchPackagesBasePath + String(zipname);
		lstrcpy(zipfile, zipPath);

		PathName unzipdir;
		unzipdir.set(zipPath);
		unzipdir.shrink(4);

		if (_waccess(zipfile, 0) != -1 && Directory::isDirectory(unzipdir))
		{
			//only add the item if you can acess it
			addItem(new PackageDescription(pkgname, pkgver, zipfile));
		}
		else
		{
			changed = true;
		}
	}

	if (num > 0)
	{
		Log::msgInformation("%ld packages loaded", num);
	}
	else
	{
		Log::msgError("No packages loaded");
	}

	return true;
}

bool8
PackageList::save(bool8 forceSave)
{
	int32 saved = 0;

	if (changed || forceSave)
	{
		TCHAR inifile[MAX_PATH + 1];
		TCHAR buf[256], which[16];
		int32 len, i;
		PackageDescription* probe;

		//
		// todo: see comments in load()
		//

		Log::msgInformation("Saving package list");

		lstrcpy(inifile, ServerOpts::patchPackagesList);

		WritePrivateProfileSection(L"Packages", L"\0\0", inifile);

		_ltow(count, buf, 10);
		WritePrivateProfileString(L"Packages", L"num", buf, inifile);

		uint32 basePathLength = ServerOpts::patchPackagesBasePath.getLength();

		i = 1;
		probe = head;
		while (probe)
		{
			if (probe->getName() && probe->getZipName())
			{
				len = 0;
				lstrcpy(buf + len, probe->getName());
				len += wcslen(buf + len);
				buf[len++] = ',';

				buf[len++] = '0';
				buf[len++] = 'x';
				_ultow(probe->getVersion(), buf + len, 16);
				len += wcslen(buf + len);
				buf[len++] = ',';

				lstrcpy(buf + len, probe->getZipName().substring(basePathLength));
				len += wcslen(buf + len);
				buf[len] = '\0';

				_ltow(i, which, 10);
				WritePrivateProfileString(L"Packages", which, buf, inifile);

				++i;
			}
			probe = probe->getNext();
		}
		
		saved = i - 1;
		changed = false;
	
		if (saved > 0)
		{
			Log::msgInformation("%ld packages saved", saved);
		}
		else
		{
			Log::msgError("No packages saved");
		}
	}

	return true;
}

int32
PackageList::strip(TCHAR* buf, int32 len)
{
	TCHAR *s, *e;

	//
	//todo: get rid of this when converting load()/save()...see notes in load()
	//

	if (len <= 0)
		return 0;

	s = buf;
	while (len > 0 &&
		(*s == L' ' || *s == L'\t' || *s == L'\n' || *s == L'\r'))
	{
		++s;
		--len;
	}

	e = s + len - 1;
	while (e > s &&
		(*e == L' ' || *e == L'\t' || *e == L'\n' || *e == L'\r'))
	{
		--e;
		--len;
	}

	s[len] = L'\0';

	if (len && s > buf)
		memmove(buf, s, len);

	return len;
}

PackageDescription*
PackageList::getItem(int32 index)
{
	PackageDescription* result = head;

	while (result && index-- > 0)
		result = result->getNext();

	return result;
}

void
PackageList::addItem(PackageDescription* item)
{
	if (item)
	{
		changed = true;
		++count;

		item->setNext((PackageDescription*)0);

		if (tail)
			tail->setNext(item);
		else
			head = item;

		tail = item;
	}
}

bool8
PackageList::removeItem(PackageDescription* kill)
{
	PackageDescription* probe = head;
	PackageDescription* prev = (PackageDescription*)0;
	
	while (probe && probe != kill)
	{
		prev = probe;
		probe = probe->getNext();
	}

	if (probe)
	{
		changed = true;
		--count;

		if (prev)
		{
			prev->setNext(probe->getNext());

			if (probe == tail)
				tail = prev;
		}
		else
		{
			head = probe->getNext();
			if (!head)
				tail = head;
		}

		return true;
	}
	return false;
}
